#!/usr/bin/env python
# coding: utf-8

# # 张量 Tensor
# 
# 张量（Tensor）是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数，这些线性关系的基本例子有内积、外积、线性映射以及笛卡儿积。其坐标在 $n$ 维空间内，有  $n^{r}$ 个分量的一种量，其中每个分量都是坐标的函数，而在坐标变换时，这些分量也依照某些规则作线性变换。$r$ 称为该张量的秩或阶（与矩阵的秩和阶均无关系）。
# 
# 张量是一种特殊的数据结构，与数组和矩阵非常相似。张量（[Tensor](https://www.luojianet.cn/docs/zh-CN/r1.7/api_python/luojianet/luojianet.Tensor.html)）是luojianet网络运算中的基本数据结构，本章主要介绍张量和稀疏张量的属性及用法。

# ## 创建张量
# 
# 张量的创建方式有多种，构造张量时，支持传入`Tensor`、`float`、`int`、`bool`、`tuple`、`list`和`numpy.ndarray`类型。
# 
# - **根据数据直接生成**
# 
# 可以根据数据创建张量，数据类型可以设置或者通过框架自动推断。

# In[1]:


from luojianet import Tensor

x = Tensor(0.1)


# - **从NumPy数组生成**
# 
# 可以从NumPy数组创建张量。

# In[2]:


import numpy as np

arr = np.array([1, 0, 1, 0])
tensor_arr = Tensor(arr)

print(type(arr))
print(type(tensor_arr))


# 初始值的类型是`numpy.ndarray`，则生成的`Tensor`数据类型与之对应。

# - **使用init初始化器构造张量**
# 
# 当使用`init`初始化器对张量进行初始化时，支持传入的参数有`init`、`shape`、`dtype`。
# 
# - `init`: 支持传入[initializer]的子类。
# 
# - `shape`: 支持传入 `list`、`tuple`、 `int`。
# 
# - `dtype`: 支持传入[luojianet.dtype]

# In[3]:


from luojianet import Tensor
from luojianet import set_seed
from luojianet import dtype as mstype
from luojianet.common.initializer import One, Normal

set_seed(1)

tensor1 = Tensor(shape=(2, 2), dtype=mstype.float32, init=One())
# 生成一个服从正态分布 N(0.01,0.0) 的随机数组用于初始化tensor2
tensor2 = Tensor(shape=(2, 2), dtype=mstype.float32, init=Normal())

print("tensor1:\n", tensor1)
print("tensor2:\n", tensor2)


# `init`主要用于并行模式下的延后初始化，在正常情况下不建议使用init对参数进行初始化。
# 
# - **继承另一个张量的属性，形成新的张量**

# In[4]:


from luojianet import ops

oneslike = ops.OnesLike()
x = Tensor(np.array([[0, 1], [2, 1]]).astype(np.int32))
output = oneslike(x)

print(output)
print("input shape:", x.shape)
print("output shape:", output.shape)


# - **输出指定大小的恒定值张量**
# 
# `shape`是张量的尺寸元组，确定输出的张量的维度。

# In[5]:


zeros = ops.Zeros()
output = zeros((2, 2), mstype.float32)
print(output)


# `Tensor`初始化时，可指定dtype，如`mstype.int32`、`mstype.float32`、`mstype.bool_`等。
# 
# ## 张量的属性
# 
# 张量的属性包括形状、数据类型、转置张量、单个元素大小、占用字节数量、维数、元素个数和每一维步长。
# 
# - 形状（shape）：`Tensor`的shape，是一个tuple。
# 
# - 数据类型（dtype）：`Tensor`的dtype，是luojianet的一个数据类型。
# 
# - 转置张量（T）：`Tensor`的转置，是一个`Tensor`。
# 
# - 单个元素大小（itemsize）： `Tensor`中每一个元素占用字节数，是一个整数。
# 
# - 占用字节数量（nbytes）： `Tensor`占用的总字节数，是一个整数。
# 
# - 维数（ndim）： `Tensor`的秩，也就是len(tensor.shape)，是一个整数。
# 
# - 元素个数（size）： `Tensor`中所有元素的个数，是一个整数。
# 
# - 每一维步长（strides）： `Tensor`每一维所需要的字节数，是一个tuple。

# In[6]:


x = Tensor(np.array([[1, 2], [3, 4]]), mstype.int32)

print("x_shape:", x.shape)
print("x_dtype:", x.dtype)
print("x_transposed:\n", x.T)
print("x_itemsize:", x.itemsize)
print("x_nbytes:", x.nbytes)
print("x_ndim:", x.ndim)
print("x_size:", x.size)
print("x_strides:", x.strides)


# ## 张量索引
# 
# Tensor索引与Numpy索引类似，索引从0开始编制，负索引表示按倒序编制，冒号`:`和 `...`用于对数据进行切片。

# In[7]:


tensor = Tensor(np.array([[0, 1], [2, 3]]).astype(np.float32))

print("First row: {}".format(tensor[0]))
print("value of bottom right corner: {}".format(tensor[1, 1]))
print("Last column: {}".format(tensor[:, -1]))
print("First column: {}".format(tensor[..., 0]))


# ## 张量运算
# 
# 张量之间有很多运算，包括算术、线性代数、矩阵处理（转置、标引、切片）、采样等，张量运算和NumPy的使用方式类似，下面介绍其中几种操作。
# 
# > 普通算术运算有：加（+）、减（-）、乘（\*）、除（/）、取模（%）、整除（//）。

# In[8]:


x = Tensor(np.array([1, 2, 3]), mstype.float32)
y = Tensor(np.array([4, 5, 6]), mstype.float32)

output_add = x + y
output_sub = x - y
output_mul = x * y
output_div = y / x
output_mod = y % x
output_floordiv = y // x

print("add:", output_add)
print("sub:", output_sub)
print("mul:", output_mul)
print("div:", output_div)
print("mod:", output_mod)
print("floordiv:", output_floordiv)


# `Concat`将给定维度上的一系列张量连接起来。

# In[9]:


data1 = Tensor(np.array([[0, 1], [2, 3]]).astype(np.float32))
data2 = Tensor(np.array([[4, 5], [6, 7]]).astype(np.float32))
op = ops.Concat()
output = op((data1, data2))

print(output)
print("shape:\n", output.shape)


# `Stack`则是从另一个维度上将两个张量合并起来。

# In[10]:


data1 = Tensor(np.array([[0, 1], [2, 3]]).astype(np.float32))
data2 = Tensor(np.array([[4, 5], [6, 7]]).astype(np.float32))
op = ops.Stack()
output = op([data1, data2])

print(output)
print("shape:\n", output.shape)


# ## Tensor与NumPy转换
# 
# Tensor可以和NumPy进行互相转换。
# 
# ### Tensor转换为NumPy
# 
# 与张量创建相同，使用 `asnumpy()` 将Tensor变量转换为NumPy变量。

# In[11]:


zeros = ops.Zeros()

output = zeros((2, 2), mstype.float32)
print("output: {}".format(type(output)))

n_output = output.asnumpy()
print("n_output: {}".format(type(n_output)))


# ### NumPy转换为Tensor
# 
# 使用`Tensor()`将NumPy变量转换为Tensor变量。

# In[12]:


output = np.array([1, 0, 1, 0])
print("output: {}".format(type(output)))

t_output = Tensor(output)
print("t_output: {}".format(type(t_output)))


# ## 稀疏张量
# 
# 稀疏张量是一种特殊张量，其中绝大部分元素的值为零。
# 
# 在某些应用场景中（比如推荐系统、分子动力学、图神经网络等），数据的特征是稀疏的，若使用普通张量表征这些数据会引入大量不必要的计算、存储和通讯开销。在这种时候就可以使用稀疏张量来表征这些数据。
# 
# luojianet现在已经支持最常用的`CSR`和`COO`两种稀疏数据格式。
# 
# 常用稀疏张量的表达形式是`<indices:Tensor, values:Tensor, shape:Tensor>`。其中，`indices`表示非零下标元素， `values`表示非零元素的值，shape表示的是被压缩的稀疏张量的形状。 在这个结构下，我们定义了`CSRTensor`、`COOTensor`和`RowTensor`三种稀疏张量结构。

# ### CSRTensor
# 
# `CSR`（Compressed Sparse Row）稀疏张量格式有着高效的存储与计算的优势。其中，非零元素的值存储在`values`中，非零元素的位置存储在`indptr`（行）和`indices`（列）中。其中：
# 
# - `indptr`: 一维整数张量, 表示稀疏数据每一行的非零元素在`values`中的起始位置和终止位置, 索引数据类型仅支持int32。
# 
# - `indices`: 一维整数张量，表示稀疏张量非零元素在列中的位置, 与`values`长度相等，索引数据类型仅支持int32。
# 
# - `values`: 一维张量，表示`CSRTensor`相对应的非零元素的值，与`indices`长度相等。
# 
# - `shape`: 表示的是被压缩的稀疏张量的形状，数据类型为`Tuple`,目前仅支持2维`CSRTensor`。
# 
# 
# 下面给出一些CSRTensor的使用示例：

# In[13]:


import luojianet as ms
from luojianet import Tensor, CSRTensor

indptr = Tensor([0, 1, 2])
indices = Tensor([0, 1])
values = Tensor([1, 2], dtype=ms.float32)
shape = (2, 4)

# CSRTensor的构建
csr_tensor = CSRTensor(indptr, indices, values, shape)

print(csr_tensor.astype(ms.float64).dtype)


# ### COOTensor
# 
# `COOTensor`用于压缩非零元素位置分布不规则的Tensor，若非零元素的个数为`N`，被压缩的张量的维数为`ndims`，则有：
# 
# - `indices`: 二维整数张量，每行代表非零元素下标。形状：`[N, ndims]`， 索引数据类型仅支持int32。
# 
# - `values`: 一维张量，表示相对应的非零元素的值。形状：`[N]`。
# 
# - `shape`: 表示的是被压缩的稀疏张量的形状，目前仅支持2维`COOTensor`。
# 
# 
# 下面给出一些COOTensor的使用示例：

# In[14]:


import luojianet as ms
import luojianet.nn as nn
from luojianet import Tensor, COOTensor

indices = Tensor([[0, 1], [1, 2]], dtype=ms.int32)
values = Tensor([1, 2], dtype=ms.float32)
shape = (3, 4)

# COOTensor的构建
coo_tensor = COOTensor(indices, values, shape)

print(coo_tensor.values)
print(coo_tensor.indices)
print(coo_tensor.shape)
print(coo_tensor.astype(ms.float64).dtype)  # COOTensor转换数据类型


# 上述代码会生成如下式所示的`COOTensor`:
# 
# $$
#  \left[
#  \begin{matrix}
#    0 & 1 & 0 & 0 \\
#    0 & 0 & 2 & 0 \\
#    0 & 0 & 0 & 0
#   \end{matrix}
#   \right]
# $$

# ### RowTensor
# 
# `RowTensor`用于压缩第零个维度稀疏的张量。若`RowTensor`的维度为`[L0, D1, D2, ..., DN ]`。第零维度的非零元素个数为`D0`, 则有`L0 >> D0`。
# 
# - `indices`: 一维整数张量，表示稀疏张量第零维度中非零元素的位置，形状为`[D0]`。
# 
# - `values`: 表示相对应的非零元素的值，形状为`[D0, D1, D2, ..., DN]`。
# 
# - `dense_shape`: 表示的是被压缩的稀疏张量的形状。
# 
# > `RowTensor`只能在`Cell`的构造方法中使用

# In[15]:


from luojianet import RowTensor
import luojianet.nn as nn

class Net(nn.Module):
    def __init__(self, dense_shape):
        super(Net, self).__init__()
        self.dense_shape = dense_shape

    def forward(self, indices, values):
        x = RowTensor(indices, values, self.dense_shape)
        return x.values, x.indices, x.dense_shape

indices = Tensor([0])
values = Tensor([[1, 2]], dtype=mstype.float32)
out = Net((3, 2))(indices, values)

print("non-zero values:", out[0])
print("non-zero indices:", out[1])
print("shape:", out[2])

